/*
 *
 *  Copyright (C) 2010 Tal Eisenberg
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package tal.drivechain.variable.types;

import java.util.HashMap;
import tal.drivechain.chain.ChainNode;
import tal.drivechain.expression.dcExpression;
import tal.drivechain.matchers.ValueMatcher;
import tal.drivechain.wildcard.Wildcard;
import tal.drivechain.parsing.Tokenizer;
import tal.drivechain.variable.Variable;
import tal.drivechain.variable.expressions.CustomExpression;

/**
 *
 * @author Main
 */
public class HashmapType extends DataType implements IndexableType {
    DataType elementType;
    
    /** Creates a new instance of ArrayType */
    public HashmapType(DataType elementType) {
        super(elementType.getName() + "[#]", HashMap.class, 200 + elementType.getTypeCode());
        this.elementType = elementType;
        if (elementType.matches(DataType.EXPR))
            throw new IllegalArgumentException("Can't create an array of expr variables.");
    }

    public Object parseValue(String value) throws IllegalArgumentException {
        if (value.charAt(0)=='(' && (Tokenizer.balanced(value, 0, '(', ')')==value.length()-1)) 
                value = value.substring(1, value.length()-1);
        Tokenizer parser = new Tokenizer(value, ',');
        HashMap hashmap = new HashMap();
        while (parser.hasMoreTokens()) {
            String token = parser.nextToken().trim();
            if (token.length()!=0) {
                token = token.trim();
                String[] keyvalue = new Tokenizer(token, ':').getTokens();
                if (keyvalue.length<2)
                    throw new IllegalArgumentException("Missing ':' sign in token: '" + token + "' in '" + value +"'");
                hashmap.put(keyvalue[0], elementType.parseValue(keyvalue[1]));
            }
        }
        return hashmap;
    }

    public DataType getElementType() { return elementType; }
        
    @Override
    public String toString(Object value, ChainNode context) {
        if (value!=null) {
            String out = "(";
            HashMap val = (HashMap)value;
            for (Object key : val.keySet()) {
                out += key + ": " + elementType.toString(val.get(key), context) + ", ";
            }
            out = out.substring(0, out.length()-2) + ")";
            return out;
                
        } else return "null-value";
    }

    @Override
    public CustomExpression createExpression(String value) throws IllegalArgumentException {
        throw new IllegalArgumentException("An expression can't be assigned to a hashmap.");
    }

    @Override
    public ValueMatcher createValueMatcher() throws IllegalArgumentException {
        throw new IllegalArgumentException("Value matchers are not supported for hashmaps.");
    }

    @Override
    public Variable evaluateVar(Variable var, ChainNode context) {
        HashMap elements = (HashMap)var.getInternalValue();
        HashMap dest = (HashMap)elements.clone();
        for (Object key : elements.keySet()) {
            Variable tmpVar = new Variable("temp", elementType, elements.get(key), true);
            dest.put(key, elementType.cast(elementType.evaluateVar(tmpVar, context).getInternalValue()));
        }
        return new Variable(var.getName(), new HashmapType(elementType), dest, true);
    }
    
    public Object setByIndex(Object workon, Variable value, dcExpression index, ChainNode context) throws IllegalArgumentException {
        HashMap hashmap = (HashMap)workon;
        String key = ((String)index.evaluate(context));
        Object valToSet;
        if (!value.getDataType().matches(elementType)) {
            valToSet = elementType.cast(value.getInternalValue());
        } else valToSet = value.getInternalValue();
        
        //put value in the hashmap
        hashmap.put(key, valToSet);
        return hashmap;
    }

    public Variable getByIndex(Wildcard index, Variable from, ChainNode context) throws IllegalArgumentException {
        HashMap hashmap = (HashMap)from.getInternalValue();
        HashMap rethashmap = new HashMap();
        String nameEnd = "";
        for (Object key : hashmap.keySet()) 
            if (index.matches(key, context)) { rethashmap.put(key, hashmap.get(key)); nameEnd += "" + key; }
        if (nameEnd.length()>8) nameEnd=nameEnd.substring(0, 5) + "$etc";
        if (rethashmap.isEmpty())
            throw new IllegalArgumentException("Can't find key to match '" + index.getWildcardString(context) + "'");
        else if (rethashmap.size()==1) {
            Object onlyKey = null;
            for (Object key : rethashmap.keySet()) onlyKey = key;
            return new Variable(from.getName() + "$" + nameEnd, elementType, rethashmap.get(onlyKey), true);
        }
        else return new Variable(from.getName() + "$" + nameEnd, this, rethashmap, true);
    }

    public boolean isNestingAllowed() {
        return true;
    }

    public Object getNextDimension(Object workon, Object modifier, ChainNode context) {
        String keyword = (String)((dcExpression)modifier).evaluate(context);
        return ((HashMap)workon).get(keyword);

    }
}